package pl.cezary.butler.walili.client;

import pl.cezary.butler.walili.client.gui.GUI;
import pl.cezary.butler.walili.common.ILoginFacadeRemote;
import pl.cezary.butler.walili.common.IServerFacadeRemote;
import pl.cezary.butler.walili.common.User;
import java.rmi.RemoteException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.concurrent.TimeUnit;
import pl.cezary.butler.walili.IServicesLocator;
import pl.cezary.butler.walili.ServicesLocator;

// <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
// </editor-fold> 
public class LoginControler extends BaseControler {

    public static final String LOGIN = "zaloguj";
    public static final String LOGOUT = "wyloguj";
    public static final String SIGNIN = "zarejestruj";
    public static final String CONNECTED_GUI = "GUI_CON";
    //TODO dodaj logger do klasy a nastepnie informacje o wazniejszych zdarzeniach drukuj w loggerze
    //jezeli zdarzenie jest normalne uzywaj poziomu info natomiast jezeli jest to powazny blad ktory nie powinien byl wystapic severe
    //warning w pozostalych przypadkach
    private static final Pattern LOGIN_PATTERN = Pattern.compile("[a-z0-9.-_!@]{5,}", Pattern.CASE_INSENSITIVE);
    private static final Pattern HOST_PATTERN = Pattern.compile("(([a-z0-9-_]+)(\\.[a-z0-9-_]+)*)|([0-2]?[0-9]{1,2}\\.){3}([0-2]?[0-9]{1,2})", Pattern.CASE_INSENSITIVE);//FIXME to wyrazenie regularnie nie jest poprawne
    private static final Pattern HASLO_PATTERN = Pattern.compile(".{5,}");
    private static final Logger log = Logger.getLogger("KontrolerLogowania");
    private Thread thPobieranie;
    IServerFacadeRemote f;
    public TaskControler taskControler;
    private Thread downloader = new Thread(new DownloadWorker(), "Downloader");    
    public QueryControler queryControler;
    private ILoginFacadeRemote fasadaLogowania;

    public LoginControler() {
        taskControler = new TaskControler();
        queryControler = new QueryControler();
    }

    private boolean tryFetchLoginFacade() {
        try {
            final IServicesLocator locator = ServicesLocator.getInstance(getZrodloDanych());
            fasadaLogowania = locator.getLoginFacade();
            return fasadaLogowania != null;
        } catch (Exception e) {
            log.log(Level.SEVERE, "Pobranie fasady logowania nie powiodło się", e);
        }
        return false;
    }

    private boolean validateLogin(String login) {
        Matcher loginMatcher = LOGIN_PATTERN.matcher(login);
        if (!loginMatcher.matches()) {
            log.log(Level.INFO, "Podano b\u0142\u0119dny login: {0}", login);
            getZrodloDanych().alert(GUI.getLocalized("loginCtl.wrong_login"));
            return false;
        }
        return true;
    }

    private boolean validatePassword(String haslo) {
        Matcher hasloMatcher = HASLO_PATTERN.matcher(haslo);
        if (!hasloMatcher.matches()) {
            log.log(Level.INFO, "Podano błędne hasło");
            getZrodloDanych().alert(GUI.getLocalized("loginCtl.wrong_password"));
            return false;
        }
        return true;
    }

    private boolean validateHost(String host) {
        Matcher hostMatcher = HOST_PATTERN.matcher(host);
        if (!hostMatcher.matches()) {
            log.log(Level.INFO, "Podano b\u0142\u0119dny adres hosta: {0}", host);
            getZrodloDanych().alert(GUI.getLocalized("loginCtl.wrong_host"));
            return false;
        }
        return true;
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        final String polecenie = arg0.getActionCommand();
        if (LOGIN.equals(polecenie)) {
            log.log(Level.INFO, "Uruchamiam logowanie");
            login();            
        } else if (LOGOUT.equals(polecenie)) {
            log.log(Level.INFO, "Uruchamiam wylogowanie");
            logout();            
        } else if (SIGNIN.equals(polecenie)) {
            singnIn();
            log.log(Level.INFO, "Uruchamiam rejestracje");
        } else if (CONNECTED_GUI.equals(polecenie)) {
            tryFetchLoginFacade();
            try {
                final boolean credentinalsNeccessary = fasadaLogowania.isCredentinalsNeccessary();                
                if(!credentinalsNeccessary){
                    login();                    
                }
                getZrodloDanych().credentialsNecessary(credentinalsNeccessary);
                
            } catch (RemoteException ex) {
                log.log(Level.SEVERE, "Sprawdzenie czy dane logowania są wymagane nie powiodło się!", ex);
            }

        } else {
            log.log(Level.SEVERE, "Podano nieznane polecenie dla kontrolera logowania");
        }
    }

    /**
     * Metoda po poprawnym podlaczeniu do serwera powinna ustawic fasade logowania na zdalny
     * obiekt sluzacy do logowania, w przeciwnym przypadku na null
     * @param Host adres serwera
     */
    private void connectToServer(String Host) {

        tryFetchLoginFacade();


    }

    private void startTasks() {
        thPobieranie = new TaskFetchingThread(getFasada());
        thPobieranie.start();
    }

    public void logout() { //potrzebuje dostepu do fasady uzytkownika, ta znajduje sie w wyzszym kontrolerze i jest statyczna, wiaże to sie z ograniczeniem liczby serwerow do ktorych mozna byc jednoczesnie podlaczonym do 1
        final IServerFacadeRemote fasada = getFasada();
        try {
            downloader.interrupt();
            if (fasada != null) {
                thPobieranie.interrupt();
                try {
                    thPobieranie.join();
                } catch (InterruptedException ex) {
                    log.log(Level.SEVERE, "Interrupted while waiting for task fetching thread thread to finish.", ex);
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException ex1) {
                        log.log(Level.SEVERE, null, ex1);
                    }
                }                
                fasada.logout();
            }
            final UserDataSource z = getZrodloDanych();
            z.enableLogowanie(true);
            z.enableZadanie(false);
            z.enableZapytanie(false);
        } catch (RemoteException e) {
            log.log(Level.INFO, "Brak fasady przy wylogowywaniu",e);
            final UserDataSource z = getZrodloDanych();
            z.enableLogowanie(true);
            z.enableZadanie(false);
            z.enableZapytanie(false);
        }
    }

    private void login() {
        final UserDataSource z = getZrodloDanych(); 
        try {
            if ((fasadaLogowania!=null && !fasadaLogowania.isCredentinalsNeccessary()) || (validateHost(z.getHost()) && validateLogin(z.getLogin()) && validatePassword(z.getPassword()))) {
                connectToServer(z.getHost());
                assert (fasadaLogowania != null) : "Zrodlo danych nie powinno pozwolic na taka sytuacje.";
                f = fasadaLogowania.zaloguj(new User(z.getLogin(), z.getPassword()));
                if (f != null) {
                    setFasada(f);
                    taskControler.setFasada(f);
                    queryControler.setFasada(f);
                    z.enableZadanie(true);
                    z.enableZapytanie(true);
                    z.enableLogowanie(false);
                    startTasks();
                    downloader.start();
                } else {
                    z.alert(GUI.getLocalized("loginCtl.login_error"));
                }
            }
        } catch (RemoteException e) {
            z.alert(GUI.getLocalized("loginCtl.connection_error"));
            log.log(Level.INFO, "Podany serwer był nieosiągalny");
        }
    }

    @Override
    public void connectDataSource(UserDataSource z) {
        super.connectDataSource(z);
        taskControler.connectDataSource(z);
        queryControler.connectDataSource(z);
        //z.podlaczKontrolerZadan(kzad);
        //z.podlaczKontrolerZapytan(kzap);
        z.podlaczKontrolerLogowania(this);
        z.enableZadanie(false);
        z.enableZapytanie(false);
    }

    private void singnIn() {
        final UserDataSource z = getZrodloDanych();
        try {
            if (validateHost(z.getHost()) && validateLogin(z.getLogin()) && validatePassword(z.getPassword())) {
                connectToServer(z.getHost());
                if (fasadaLogowania != null) {
                    List<String> err = fasadaLogowania.zarejestruj(new User(z.getLogin(), z.getPassword()));
                    if (err.isEmpty()) {
                        IServerFacadeRemote f = fasadaLogowania.zaloguj(new User(z.getLogin(), z.getPassword()));
                        if (f != null) {
                            setFasada(f);
                            taskControler.setFasada(f);
                            queryControler.setFasada(f);
                            z.enableZadanie(true);
                            z.enableZapytanie(true);
                            z.enableLogowanie(false);
                            startTasks();
                        } else {
                            z.alert(GUI.getLocalized("loginCtl.login_error"));
                        }
                    } else {//TODO: tlumaczenie na inne jezyki komunikatu (na kliencie) na serwerze inne formulowanie komunikatu
                        z.alert(err.toString());
                        log.log(Level.SEVERE, "Błąd rejestracji  użytkownika login:" + z.getLogin() + " hasło:" + z.getPassword());
                        for (String el : err) {
                            log.log(Level.SEVERE, el);
                        }
                    }
//                } else {
//                    IFasadaSerweraRemote f = fasadaLogowania.zaloguj(new User(z.getLogin(), z.getHaslo()));
//                    if (f != null) {
//                        setFasada(f);
//                        kzad.setFasada(f);
//                        kzap.setFasada(f);
//                        z.enableLogowanie(false);
//                        z.enableZadanie(true);
//                        z.enableZapytanie(true);
//                        rozpocznijWykonywanieZadan();
//                    } else {
//                        z.alert("Błąd logowania spróbuj później");
//                    }
//                }
                } else {
                    z.alert(GUI.getLocalized("loginCtl.connection_error"));
                    log.log(Level.INFO, "Podany serwer był nieosiągalny");
                }
            }
        } catch (RemoteException e) {
            z.alert(GUI.getLocalized("loginCtl.connection_error"));
            log.log(Level.INFO, "Podany serwer był nieosiągalny");
        }
    }
}
